Inversion of Control implemented using Dependency Injection, implemented by:
- setter method taking a resource
- constructor method taking a resource
- interface injection (implement an interface that takes a set of resources and uses the required ones)
setter and constructor are more widely supported.
spring core implements a container BeanFactory
spring context implements a more advanced container ApplicationContext with i18N and other features
spring also has MVC, portlet MVC, testing, and integration with other ORM and web frameworks
spring projects include:
- spring IDE - plugin for eclipse
- spring security - authentication, authorization, access control framework
- spring web flow - model user interactions within a web app as flows
- spring web services - contract first and document driven web services
- spring rich client - rich GUI applications developed with spring
- spring batch - batch processing
- spring modules - misc tools?
- spring dynamic modules - supports creating spring apps to run on osgi platform which allows dynamic
installation, updating, loading, unloading
- spring integration - support enterprise integration with external systems
- spring ldap
- spring javaconfig - java based alternative to configuring components
- spring beandoc - generate documentation and diagrams based on bean config file
- spring .net - .net version of spring
Installing Spring IDE Tool: http://blog.springsource.com/2009/06/24/installing-sts-into-eclipse-35/
include these jars in classpath:
- dist/spring.jar (or dist/modules/whateveryouneed)
- lib/jakarta-commons/commons-logging.jar
---------
Guide Based on http://static.springsource.org/docs/Spring-MVC-step-by-step/
Create regular Web Project
Add the spring dispatcher to web.xml:
springapporg.springframework.web.servlet.DispatcherServlet1springapp*.htm
Create a file for spring MVC with pattern -servlet.xml to specify which url is handled by which controller:
Copy dist/spring.jar, dist/modules/spring-webmvc.jar, lib/jakarta-commons/commons-logging.jar to WEB-INF/lib
Create controller classes specified in *-servlet.xml:
package springapp.web;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.IOException;
public class HelloController implements Controller {
protected final Log logger = LogFactory.getLog(getClass());
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
logger.info("Returning hello view");
return new ModelAndView("hello.jsp");
}
}
Controllers handle the request and return a ModelAndView. The model is resolved via a ViewResolver - in this case a default one is used.
Create the view hello.jsp:
Hello :: Spring Application
Hello - Spring Application
Greetings.
Write a test for the controller, also need to add copy junit 3.8.2 or 4.4 to web-inf/lib:
package springapp.tests;
import org.springframework.web.servlet.ModelAndView;
import springapp.web.HelloController;
import junit.framework.TestCase;
public class HelloControllerTests extends TestCase {
public void testHandleRequestView() throws Exception{
HelloController controller = new HelloController();
ModelAndView modelAndView = controller.handleRequest(null, null);
assertEquals("hello.jsp", modelAndView.getViewName());
}
}
To run a JUnit test:
Window > Open View > JUnit
right-click on a class that is subclass of TestCase, Run > JUnit
Will run all methods in class that are test*
If there is a method called setUp, it is run before the tests (can be used to get data etc)
JSTL & JSP header
Copy lib/j2ee/jstl.jar (JSP Standard Tag Library) and lib/jakarta-taglibs/standard.jar to WEB-INF/lib
Create include.jsp in WEB-INF/jsp:
<%@ page session="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
JSTL Core:
<%@ include file="/WEB-INF/jsp/include.jsp" %>
$
JSTL Formatter - formatting and internationalisation:
- page = this page
- request = all pages in this request
- session = all requests in this session
- application = all requests for all users
- scope is optional, value can indicate country like en_US
apply only to this block
-- formatted date is put in var if var specified, else printed
fmt:parseDate and parseNumber can be used to read a string into a number/date variable
messages..params.. prefix is applied to key automatically
http://java.sun.com/products/jsp/jstl/1.1/docs/tlddocs/index.html
Decoupling the view from the controller:
In -servlet.xml, add:
Now any view string returned will automatically have /WEB-INF/jsp prepended and .jsp appended. This is done by spring and uses InternalResourceViewResolver. JstlView allows us to support internationalization and use JSTL.
Typical design involves writing a class to hold data, a class to manage the data and test classes. Managing classes are defined by an interface. Controller class has a setter (IoC) to receive the manager object thus avoiding dependency on any manager implementation and only depending on the interface.
Setting values in objects and passing them through the IoC setter:
In -servlet.xml:
...
Setting a source for messages:
Create messages.properties in WEB-INF/classes which is read when fmt:message key is called
title=SpringApp
heading=Hello :: SpringApp
greeting=Greetings, it is now
Spring's form tag library:
copy dist/resources/spring-form.tld to WEB-INF/tld
Create taglib entry in web.xml:
/spring/WEB-INF/tld/spring-form.tld
in JSP file:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
Create a bean with same name as commandName (PriceIncrease) with setters for all input.. i.e. get/setPercentage
Create a validator class with name PriceIncreaseValidator:
import org.springframework.validation.Validator;
import org.springframework.validation.Errors;
public class PriceIncreaseValidator implements Validator {
public boolean supports(Class clazz) {
return PriceIncrease.class.equals(clazz);
}
public void validate(Object obj, Errors errors) {
...
errors.rejectValue("percentage", "error.not-specified", null, "Value required.");
errors.rejectValue("percentage", "error.too-high/low",
new Object[] {new Integer(maxPercentage)}, "Value too high/low.");
}
}
Add a form controller:
Add an entry in *-servlet.xml to define the new form and controller:
- commandClass and validator are injected
- formView (show form) and successView (successful process)
- successView can be a regular view reference forwarded to JSP (refresh causes resubmit) or redirect
The form controller class:
flow: http://static.springsource.org/spring/docs/2.0.8/api/org/springframework/web/servlet/mvc/AbstractFormController.html
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
public ModelAndView onSubmit(Object command) throws ServletException {
PriceIncrease formObj = (PriceIncrease) command;
int percent = formObj.getPercentage();
productManager.increasePrice(increase);
return new ModelAndView(new RedirectView(getSuccessView())); // redirect to success view
}
protected Object formBackingObject(HttpServletRequest request) throws ServletException {
// return a preconfigured PriceIncrease object (which may be reconfigured later)
// this will set form defaults
}
public void setProductManager(ProductManager productManager) ...
public ProductManager getProductManager()...
Setting default error messages:
required=Entry required.
typeMismatch=Invalid data.
typeMismatch.percentage=That is not a number!!!
Using JDBC with Spring:
Create an interface (good practise but not required)
Implement the interface with Spring's JDBC abstraction framework - which handles opening/closing connections and statements:
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.ParameterizedRowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcDaoSupport;
// SimpleJdbcDaoSupport provides convenient access to an already configured SimpleJdbcTemplate
public class JdbcProductDao extends SimpleJdbcDaoSupport implements ProductDao {
...
// selecting data:
List products = getSimpleJdbcTemplate().query(
"select id, description, price from products",
new ProductMapper());
// updating data:
int count = getSimpleJdbcTemplate().update(
"update products set description = :description, price = :price where id = :id",
new MapSqlParameterSource().addValue("description", prod.getDescription())
.addValue("price", prod.getPrice())
.addValue("id", prod.getId()));
// resultset to product mapper:
private static class ProductMapper implements ParameterizedRowMapper {
public Product mapRow(ResultSet rs, int rowNum) throws SQLException {
Product prod = new Product();
prod.setId(rs.getInt("id"));
prod.setDescription(rs.getString("description"));
prod.setPrice(new Double(rs.getDouble("price")));
return prod;
}
}
}
Spring test framework
Copy spring-test.jar from dist/modules to WEB-INF/lib
for datasource testing create a new class extending AbstractTransactionalDataSourceSpringContextTests
- is transactional - any changes we made are rolled back at the end
- we get dependency injection (configured in xml file returned by getConfigLocations method)
- onSetUpInTransaction lets us load test data, etc
- deleteFromTables(String[] tables) can be called to empty existing tables
- executeSqlScript("filename", true) can be called to execute a script (to load data etc)
in xml file returned by getConfigLocations:
classpath:jdbc.properties
and jdbc.properties in WEB-INF/classes:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost
jdbc.username=root
jdbc.password=a1b3c2a
Using JDBC classes we created:
To inject a DAO class, create WEB-INF/applicationContext.xml, which is loaded when you add this to web.xml:
org.springframework.web.context.ContextLoaderListener
productManager = new SimpleProductManager();
productManager.setProductDao(new InMemoryProductDao(products));
AspectJ:
- Basic idea of AOP is to consolidate all cross-cutting code (debugging, logging, transactions, security, ...) that are normally spread throughout into one location for easier maintenance.
- Join points are points where aspects are applied to code. In AspectJ they are specified by "pointcuts" which are method signatures.. examples:
- "execution(* set*(*))" matches method execution join point if the method name starts with set and has 1 arg of any type
- "this(Point)" - matches when current object is an instance of class Point
- "within(com.company.*)" - matches any join point in any type in the com.company package
- Pointcuts can be named for reuse: pointcut set() : execution(* set*(*)) && this(Point)
and later.. after() : set() { Display.update(); }
above code runs Display.update() after the join point
Adding transaction and connection pool configuration to app context:
Copy aspectjweaver.jar from lib/aspectj and commons-dbcp.jar and commons-pool.jar from lib/jakarta-commons to WEB-INF/lib
applicationContext.xml:
"execution(* *..ProductManager.*(..))" matches any method called on the ProductManager interface
Also in applicationContext.xml, set up the connection pool:
classpath:jdbc.properties